package figure8;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.UUID;

import javax.sound.midi.Instrument;
import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiChannel;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Patch;
import javax.sound.midi.Receiver;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.midi.Synthesizer;
import javax.sound.midi.Transmitter;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;

import figure8.GUI.MIDITrackPanel;
import figure8.Network.Client;
import figure8.Network.Message;
import figure8.Network.NetworkConnection;

public class MIDITrack extends BaseTrack {
	private Sequencer seq;
	private File file;
	private Sequence sequence;
	private Synthesizer synth;
	private int start, instrumentNum;

	public static MIDITrack fromString(Project project, String serialized) {
		MIDITrack track = null;
		try {
			File file = new File(project.getLocation().getAbsoluteFile(), "Recordings/" + UUID.randomUUID() + ".midi");
			file.createNewFile();
			
			track = new MIDITrack(file, false);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			System.err.println("Couldn't create blank file :(");
		} catch (MidiUnavailableException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			System.err.println("Couldn't midi :(");
		} catch (InvalidMidiDataException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			System.err.println("Couldn't midi :(");
		}
		track.deserializeMetadata(serialized);
		
		return track;
	}
	
	public MIDITrack(File inputFile, boolean ready) throws MidiUnavailableException,
	InvalidMidiDataException, IOException {
		super("Unnamed MIDITrack", false, false, 0, 0);
		
		seq = MidiSystem.getSequencer(false);
		seq.open();
		file = inputFile;
		//sequence = MidiSystem.getSequence(midiFile);
		//seq.setSequence(sequence);
		synth = MidiSystem.getSynthesizer();
		synth.open();
		instrumentNum = 0;
		Receiver synthReceiver = synth.getReceiver();
		//Transmitter seqTransmitter = seq.getTransmitter();
		//seqTransmitter.setReceiver(synthReceiver);
	}
	
	@Override()
	public File getFile() {
		return this.file;
	}
	
	@Override()
	public void finalize() {
		super.finalize();
		
		try {
			sequence = MidiSystem.getSequence(file);
			seq.setSequence(sequence);
			Transmitter seqTransmitter = seq.getTransmitter();
			seqTransmitter.setReceiver(synth.getReceiver());
		} catch (InvalidMidiDataException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (MidiUnavailableException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public MIDITrack(File inputFile) throws MidiUnavailableException,
			InvalidMidiDataException, IOException {
		super("Unnamed MIDITrack", false, false, 0, 0);
		
		seq = MidiSystem.getSequencer(false);
		seq.open();
		file = inputFile;
		sequence = MidiSystem.getSequence(file);
		seq.setSequence(sequence);
		synth = MidiSystem.getSynthesizer();
		synth.open();
		instrumentNum = 0;
		Receiver synthReceiver = synth.getReceiver();
		Transmitter seqTransmitter = seq.getTransmitter();
		seqTransmitter.setReceiver(synthReceiver);
	}
	
	public void changeInstrument(int instrumentNumber) {
		Instrument[] instruments  = getInstruments();
		
		//store instrumentNum in save file so the correct instrument can be reloaded
		instrumentNum = instrumentNumber;
		Instrument instrument = instruments[instrumentNum];
		Patch patch = instrument.getPatch();
	    MidiChannel[] channels = synth.getChannels();
	    for (int i=0; i<channels.length; i++) {
	        channels[i].programChange(patch.getBank(),
	        	    patch.getProgram());
	    }
	}
	
	public Instrument[] getInstruments() {
		return synth.getDefaultSoundbank().getInstruments();
	}

	@Override
	public void play() {
		seq.setMicrosecondPosition(0);
		seq.start();
	}
	
	public int getInstrumentNumber () {
		return instrumentNum;
	}

	@Override
	public void stop() {
		seq.stop();
		seq.setMicrosecondPosition(0);
	}
	
	@Override
	public void setMuted(boolean muted) {
		super.setMuted(muted);
		
		seq.setTrackMute(0, muted);
	}
	
	@Override
	public MIDITrackPanel makeTrackPanel(Controller controller) {
		return new MIDITrackPanel(controller, this);
	}

	@Override
	public void setVolume(float volume) {
	    super.setVolume(volume);
		
		MidiChannel[] channels = synth.getChannels();
		
	    for (int i=0; i<channels.length; i++) {
	        channels[i].controlChange(7, (int)(((volume + 30) / 44) * 127.0));
	    }
	}

	@Override
	public void pause() {
		
	}

	@Override
	public Boolean[][] getDrumMachine() {
		return null;
	}

	@Override
	public void send(NetworkConnection client, Client exclude) {
		System.out.println("Sending MIDITrack " + this.getName());
		
		String data = "MIDI " + this.serializeMetadata();
		
		client.send(new Message(Message.Type.TRACK_ADD, data));
		
		this.sendFile(client, exclude);
	}

	@Override
	public Type getType() {
		return Type.MIDI;
	}

    @Override
    public String serializeMetadata() {
        return super.serializeMetadata() + " " + this.file.length();
    }

    public String deserializeMetadata(String metadata) {
        String[] parts = super.deserializeMetadata(metadata).split(" ", 2);
        
        this.size = Integer.parseInt(parts[0]);
        
        String leftover;
        
        if (parts.length < 2) {
                leftover = "";
        } else {
                leftover = parts[3];
        }
        
        return leftover;
    }
}
